using UnityEngine;
using System.Collections;

[RequireComponent(typeof(CharacterController))]

public class EnemyControl : MonoBehaviour
{
    #region Enumerados

    public enum AIState { Guard, Patrol, Alert, Attack }   // enumeracion de posibles estados IA
    public AIState state = AIState.Patrol;          // estado activo

    public enum AIMove { Vertical, Horizontal, Ruta, SoloGiro }    // enumeracion posibles movimientos del enemigo
    public AIMove movimiento = AIMove.Horizontal;           // movimiento activo del enemigo

    #endregion

    #region variables movimiento

    public float speed = 0.1f;                      // velocidad de movimiento
    private float speedInit;                        // var interna usada para reestablecer la velocidad de movimiento
    public float rotationSpeed = 5.0f;               // Vel de giro
    private float rotationSpeedInit;                 // var interna usada para reestablecer la velocidad de giro
    private float initRotateX;
    private float initRotateZ;
    public float GuardTime = 3;                     // Tiempo de espera en un punto. Estando en modo guardia.
    private float GuardTimeCheck = 0;               // Var Interna que controla el ratio del tiempo de modo Guardia.
    public float WaitTime = 1;                      // Tiempo de espera antes de darse la vuelta. SOLO en movimiento H y V.
    public float ajusteDistancia = 1;               //  Distancia minima de proximidad a un punto. SOLO en ruta
    public DirectionalWaypoint activeWaypoint;      // Waypoint al que se dirige. SOLO en ruta
    public float giroInicio = 0;                    // angulo de giro inicial. SOLO giro
    public float giroFinal = 0;                     // angulo de giro final. SOLO giro
    private bool giroCompleto = false;              // controla la direccion del giro. SOLO giro
    private Vector3 moveDirection = Vector3.zero;   // vector direccion

    #endregion

    #region variables Vision

    public float anguloVision = 60;                 // angulo de vision
    public float rangoVision = 5;                  // rango de vision => radio del trigger "RangoVision"
    public float sightCheckTime = 1;                 // tiempo necesario para considerar visto al player
    [HideInInspector]
    public bool bSightCheckTime = true;             // variable para evitar llamar multiples corrutinas
    [HideInInspector]
    public bool sightPlayer = false;                // variable que guarda el estado de si se esta viendo al player

    #endregion

    #region controllers

    private CharacterController controller;         // el character controller
    private PlayerControl player = null;           // Objetivo, osea, el player

    #endregion

    void Awake()
    {
        


    }

    void Start()
    {
        // Buscamos al Player por Tag.
        GameObject playerGO = GameObject.FindGameObjectWithTag(StringConstants.TAG_PLAYER);
        if (playerGO)
            player = playerGO.GetComponent<PlayerControl>();
        else
        {
            // Si no tenemos el objetivo.
            Debug.Log("There is no entity tagged 'Player' in the scene, shoud be your character");
            return;
        }

        // Comprobamos que tengamos waypoint para patrullar (minimo 2)
        if (movimiento == AIMove.Ruta)
        {
            if (!activeWaypoint)
            {
                Debug.Log("You need to add any number of waypoint - 2 minimun");
                return;
            }
        }

        // inicializamos el controller
        controller = GetComponent<CharacterController>();

        // guardamos velocidad inicial
        speedInit = speed;
        rotationSpeedInit = rotationSpeed;
        // guardamos posicion inicial
        initRotateX = this.transform.rotation.x;
        initRotateZ = this.transform.rotation.z;
//        Debug.Log("X: " + initRotateX + " Z: " + initRotateZ);
    }

    void Update()
    {
        CheckSight(sightCheckTime);

        /*  
         * probando forma alternativa. con este metodo puede verle al instante, o al paso de ese tiempo
        if (bSightCheckTime)
        {
            // corrutina ejecutada cada "sightCheckTime" que calcula si se esta viendo al player
            StartCoroutine(CheckSight(sightCheckTime));
        }
        */
        switch (state)
        {
            case AIState.Patrol:
                // Accion (mover)
                MoveTowards();

                // Comprobar condiciones para cambio de estado.
                // si ha llegado a su destino. 
                //se comprueba en la funcion de movimiento

                // si vemos al player
                if (sightPlayer)
                    state = AIState.Attack;

                break;
            case AIState.Guard:
                // Action
                if (speed > 0)
                {
                    speed = 0;                                  // se detiene
                    rotationSpeed = 0;
                    GuardTimeCheck = Time.time + GuardTime;     // establece el tiempo de espera
                }

                // condiciones de cambio
                // si a pasado el tiempo
                if (Time.time > GuardTimeCheck)                 // si ha pasado el tiempo de espera
                {
                    state = AIState.Patrol;                     // volvemos a patrullar
                    speed = speedInit;                          // reestablecemos velocidad movimiento
                    rotationSpeed = rotationSpeedInit;

                }

                // si vemos al player
                if (sightPlayer)
                    state = AIState.Attack;

                break;
            case AIState.Alert:
                // Action
                // moverse hasta posible posicion player

                // condiciones cambio
                // si se encuentra al player
                if (sightPlayer)
                    state = AIState.Attack;

                // si no se encuentra
                // dejamos de buscar. vuelve a patrullar


                /*      SearchPlayer(lastVisiblePlayerPosition);

                      // Player not visible anymore - stop attacking
                      if (Time.time >= ChaseTimeCheck)
                      {
                          if (!CanSeeTarget())
                          {
                              Debug.Log("Back to Patrol.");
                              speed = 1;
                              //SendMessage("SetSpeed", 1);
                              state = AIState.Patrol;
                              return;
                          }
                          else
                          {
                              Debug.Log("Back to Attack.");
                              state = AIState.Attach;
                          }
                      }
                 */
                break;
            case AIState.Attack:
                // te han pillado. se acabo el juego.
                Debug.Log("te han pillado. se acabo el juego.");
                //   yield return new WaitForSeconds(5);
                //  Application.Quit();
                break;
        }
    }

    void CheckSight(float sightCheckTime)
    {
        //        bSightCheckTime = false;

        // esperamos el tiempo
        //        yield return new WaitForSeconds(sightCheckTime);

        // calculamos vision
        if (bSightCheckTime)
        {
            if (isInCone())
            {
                Debug.Log("DENTRO DEL CONO!!");
                StartCoroutine(StillOnSight(sightCheckTime));
            }

        }

        //        bSightCheckTime = true;
    }

    IEnumerator StillOnSight(float sightCheckTime)
    {
        bSightCheckTime = false;

        // esperamos el tiempo
        yield return new WaitForSeconds(sightCheckTime);

        if (isInCone())
        {
            Debug.Log("-- AUN DENTRO DEL CONO!!");
            // si esta dentro, hay obstaculos enfrente?
            if (isInFront())
            {
                Debug.Log("esta enfrente!! ");

                //sightPlayer = true;
            }
            else
            {
                Debug.Log("hay obstaculo");

                //sightPlayer = false;
            }
        }
        bSightCheckTime = true;
    }

    private bool isInCone()
    {
        // distancia de enemy a player
        float distance = Vector3.Distance(player.transform.position, transform.position);

        // angulo
        Vector3 playerDir = player.transform.position - transform.position;
        Vector3 enemyDir = transform.forward;
        float angulo = Vector3.Angle(playerDir, enemyDir);

        // comprobamos si esta dentro del cono vision
        if ((angulo < anguloVision) && (distance < rangoVision))
            return true;
        else
            return false;
    }

    private bool isInFront()
    {
        // si el rayo no choca contra la mascara, es el player
        int mask = 1 << LayerMask.NameToLayer(StringConstants.TAG_OBSTACULO);
        if (!Physics.Linecast(transform.position, player.transform.position, mask))
        {
            // Debug.Log("esta enfrente!! ");
            return true;
            //sightPlayer = true;
        }
        else
        {
            // Debug.Log("hay obstaculo");
            return false;
            //sightPlayer = false;
        }
    }



    private void MoveTowards()
    {

        switch (movimiento)
        {
            case AIMove.Horizontal:
            case AIMove.Vertical:

                Vector3 fwd = transform.TransformDirection(Vector3.forward);
                RaycastHit impacto;

                if (Physics.Raycast(transform.position, fwd, out impacto))
                {
                    moveDirection = impacto.point - transform.position;
                    Debug.DrawLine(transform.position, impacto.point);
                }

                float modificador = Vector3.Dot(fwd, moveDirection.normalized);
                modificador = Mathf.Clamp01(modificador);

                // movimiento
                moveDirection = fwd * speed * modificador;
                controller.Move(moveDirection);            // move no usa gravedad. simplemove usa gravedad estandar.

                // llegara a su destino cuando choque con algo
                break;
            case AIMove.SoloGiro:
                if (!giroCompleto)
                {
                    // rotamos
                    RotateTowards(giroFinal);


                }
                else
                {
                    // rotamos
                    RotateTowards(giroInicio);

                }
                // linea debug
                Vector3 fwds = transform.TransformDirection(Vector3.forward);
                if (Physics.Raycast(transform.position, fwds, out impacto))
                {
                    Debug.DrawLine(transform.position, impacto.point);
                }

                break;
            case AIMove.Ruta:
                Vector3 posObjetivo = activeWaypoint.transform.position;
                Vector3 direccion = posObjetivo - transform.position;

                // rotamos hacia objetivo
                RotateTowards(direccion);

                // Modify speed so we slow down when we are not facing the target
                Vector3 forward = transform.TransformDirection(Vector3.forward);
                float speedModifier = Vector3.Dot(forward, direccion.normalized);
                speedModifier = Mathf.Clamp01(speedModifier);

                // Move the character
                direccion = forward * speed * speedModifier;
                controller.SimpleMove(direccion);

                // comprobamos destino
                if (activeWaypoint.CalculateDistance(transform.position) < ajusteDistancia)
                {
                    state = AIState.Guard;
                    activeWaypoint = activeWaypoint.CalculateTargetPosition(transform.position, ajusteDistancia);
                }

                break;
        }
    }

    // Rotar grados concretos  (solo la rotacion, sin movimiento).
    private void RotateTowards(float angulo)
    {

        Quaternion ending = Quaternion.Euler(initRotateX, angulo, initRotateZ);
        
        transform.rotation = Quaternion.RotateTowards(transform.rotation, ending, rotationSpeed * Time.deltaTime);

        if (transform.rotation == ending)
        {
            //Debug.Log("cambiamos girooo");
            state = AIState.Guard;
            giroCompleto = !giroCompleto;
        }
    }

    // Rotar 180 para encarar un punto  (solo la rotacion, sin movimiento).
    private void RotateTowards180()
    {
        //Debug.Log("media vuelta");
        Vector3 rotatingAxis = new Vector3(0, 180, 0);

        Quaternion startPosition = transform.rotation;
        Quaternion endPosition = startPosition * Quaternion.Euler(rotatingAxis);

        transform.rotation = Quaternion.Slerp(startPosition, endPosition, 1);
        transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
    }

    // Rotar para encarar un punto (solo la rotacion, sin movimiento).
    private void RotateTowards(Vector3 direccion)
    {
        // Rotamos hacia el blanco.
        transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direccion), rotationSpeed * Time.deltaTime);
        transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
    }

    // Rotar cuando se encuentra un obstaculo
    IEnumerator OnTriggerEnter(Collider hit)
    {
        // choca con pared u objeto
        if (hit.gameObject.tag == StringConstants.TAG_OBSTACULO)
        {
            //Debug.Log("colision obstaculo");
            state = AIState.Guard;
            if ((movimiento == AIMove.Horizontal) || (movimiento == AIMove.Vertical))
            {
                yield return new WaitForSeconds(WaitTime);
                //Debug.Log("movimiento V o H");
                RotateTowards180();                               // giramos a la nueva direccion
            }
        }

    }


}
